 package com.gmrz.uaf.protocol.v1.validaton;
 
 import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

 import com.gmrz.uaf.db.UAFDAOFactory;
 import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.db.DAOFactory;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.db.dao.AuthenticatorSpecDAO;
import com.gmrz.uaf.policy.verification.Matcher;
import com.gmrz.uaf.policy.verification.PolicyVerificationException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.AAID;
import com.gmrz.uaf.protocol.v1.schema.AuthenticatorSpec;
import com.gmrz.uaf.protocol.v1.schema.MatchCriteria;
import com.gmrz.uaf.protocol.v1.schema.Policy;
import com.gmrz.util.db.DBUtil;
 
 public class PolicyValidator implements Validator<Policy> {
   private UAFDBConnectionMgr dbutil = null;
   private int maxNumberOfMatchCriteriasAllowed = 0;
   private static final Logger LOG = LogManager.getLogger(PolicyValidator.class);
 
   @Inject
   private void setDBUtil(UAFDBConnectionMgr dbutil) {
     this.dbutil = dbutil;
   }
 
   @Inject
   private void setMaxNumberOfMatchCriterias(
		   @Named("uaf.policy.max.match.criterias.allowed")   int maxNumberOfMatchCriterias) {
     this.maxNumberOfMatchCriteriasAllowed = maxNumberOfMatchCriterias;
   }
 
   public void validate(Policy policy) throws PolicyValidationException {
     try {
       if (policy == null) {
         LOG.error("Failed to validate NULL policy.");
         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
       }
       if ((policy.getAccepted() == null) || (policy.getAccepted().size() == 0)) {
         LOG.error(" Failed to validate NULL Accepted MatchCriteria in the policy[{}]", policy.getPolicyName());
         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
       }
 
       int numberOfMatchCriteriasinPolicy = 0;
 
       List<AuthenticatorSpec> authenticatorSpecs = getAuthenticatorSpecs();
 
       if (policy.getDisallowed() != null) {
         LOG.debug("validate disallowed MatchCriterias.");
         for (MatchCriteria disAllowedMatchCriteria : policy.getDisallowed()) {
           validateMatchCriteria(disAllowedMatchCriteria);
         }
         LOG.debug("completed validating disallowed MatchCriterias.");
       }
 
       LOG.debug(" validate the accepted[][] matchCriteria in the policy");
       List<List<MatchCriteria>> acceptedMatchCriteria = policy.getAccepted();
       boolean matchSuccessful = false;
 
       LOG.debug("Now Iterating all match criterias and atleast one AAID must match each criteria for policy : {} ",
               policy.getPolicyName());
       Iterator<List<MatchCriteria>> acceptedMatchCriteriaIndexIterator = acceptedMatchCriteria.iterator();
       while (acceptedMatchCriteriaIndexIterator.hasNext()) {
         List<MatchCriteria> acceptedMatchCriteriaIndex = acceptedMatchCriteriaIndexIterator.next();
         for (MatchCriteria matchCriteria : acceptedMatchCriteriaIndex) {
           numberOfMatchCriteriasinPolicy++;
           matchSuccessful = false;
 
           validateMatchCriteria(matchCriteria);
 
           if (matchCriteria.getAAID() != null) {
             LOG.debug("MatchCriteria contains list of AAIDs, validate all of these are available.");
             checkAuthenticatorMetadataAvailable(matchCriteria, authenticatorSpecs);
             matchSuccessful = true;
           } else {
	             for (AuthenticatorSpec authenticatorSpec : authenticatorSpecs) {
	               LOG.debug("Check if following AAID matches the criteria : {}", authenticatorSpec.getAAID().format());
	 
	               if (Matcher.compare(matchCriteria, authenticatorSpec)) {
	                 matchSuccessful = true;
	                 LOG.debug("Policy MatchCriteria is matched to authenticator metadata AAID :", authenticatorSpec.getAAID().format());
	               }
	             }
           }
 
           if (!matchSuccessful) {
             LOG.error("There is no authenticator that matches the criteria, unable to load the policy[{}]", policy.getPolicyName());
             throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
           }
         }
       }
 
       LOG.debug("Number of MatchCriterias in policy : {} and Max allowed per policy : {}", Integer.valueOf(numberOfMatchCriteriasinPolicy), Integer.valueOf(getMaxMatchCriteriasPerPolicy()));
       if (numberOfMatchCriteriasinPolicy > getMaxMatchCriteriasPerPolicy()) {
         LOG.error("The number of matchCriterias in this policy: " + numberOfMatchCriteriasinPolicy + " exceed the configured value : " + getMaxMatchCriteriasPerPolicy());
 
         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
       }
       LOG.info("Completed validation of the following policy : " + policy.getPolicyName());
     } catch (Exception e) {
       LOG.error("Failed validation of policy : " + e);
       throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
     }
   }
 
   private void validateMatchCriteria(MatchCriteria matchCriteria) throws PolicyValidationException {
     if (matchCriteria == null) {
       LOG.error("Failed policy validation: NULL matchCriteria");
       throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
     }
     if ((matchCriteria.getAAID() == null) || (matchCriteria.getAAID().size() == 0)) {
	       LOG.debug(" AAID list is null, now check for supported schemes and supported algos, must be present");
	       if ((matchCriteria.getSupportedSchemes() == null) || (matchCriteria.getSupportedSchemes().size() == 0)) {
	         LOG.error("Invalid matchCriteria for policy, supported schemes must be present");
	         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
	       }
	       if ((matchCriteria.getSupportedAuthAlgs() == null) || (matchCriteria.getSupportedAuthAlgs().size() == 0)) {
	         LOG.error("Invalid matchCriteria for policy, suported algos must be present ");
	         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
	       }
     } else {
	       LOG.debug(" AAID list is available, now check for keyID, attachment and exts only to be present ");
	 
	       boolean flag = (matchCriteria.getSupportedSchemes() != null) || (matchCriteria.getSupportedAuthAlgs() != null) || 
	    		   (matchCriteria.getVendorID() != null) || (matchCriteria.getKeyProtection() != null) || 
	    		   (matchCriteria.getMatcherProtection() != null) || (matchCriteria.getTcDisplay() != null) || 
	    		   (matchCriteria.getPreferredAttestationTypes() != null) || (matchCriteria.getUserVerification() != null);
	 
	       if (flag) {
	         LOG.error("If AAID list is available then only keyID, attachment and exts are allowed in match criteria.");
	         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
	       }
	       if ((matchCriteria.getAuthenticatorVersion() != null) && (matchCriteria.getAAID().size() > 1)) {
	         LOG.error("If authenticator version is present in match criteria, then AAID list should have a single authenticator.");
	         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
	       }
     }
     LOG.debug("Completed Validation for match Criteria");
   }
 
   private List<AuthenticatorSpec> getAuthenticatorSpecs() throws Exception {
     Connection conn = null;
     try {
       conn = UAFDBConnectionMgr.getConnection(true);
//       AuthenticatorSpecDAO specdao = GuiceUtil.getProcessorInjector().getInstance(DAOFactory.class).
//    		   createAuthenticatorSpecDAO(conn);
         AuthenticatorSpecDAO specdao = UAFDAOFactory.createAuthenticatorSpecDAO(conn);
         return specdao.list();
     } finally {
       DBUtil.close(conn);
     }
   }
 
   private void checkAuthenticatorMetadataAvailable(MatchCriteria matchCriteria, 
		   List<AuthenticatorSpec> authenticatorSpecs) throws PolicyValidationException {
     if ((matchCriteria == null) || (authenticatorSpecs == null)) {
       LOG.error("Failed policy validation: matchCriteria or specs cannot be null");
       throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
     }
 
     List<String> aaidsInAuthenticatorSpecs = new ArrayList<String>();
 
     LOG.debug("List of authenticator specs in the database: ");
     for (AuthenticatorSpec spec : authenticatorSpecs) {
       if (spec.getAAID() == null) {
         LOG.error("Failed policy validation: AAID cannot be null in authenticator spec");
         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
       }
       LOG.debug("AAID : " + spec.getAAID().format());
       aaidsInAuthenticatorSpecs.add(spec.getAAID().format());
     }
 
     for (AAID aaid : matchCriteria.getAAID())
       if (!aaidsInAuthenticatorSpecs.contains(aaid.format())) {
         LOG.error("Failed policy validation. MatchCriteria contains AAID : " + aaid.format() + 
        		 ", not found in the authenticatoe metadata list in database.");
         throw new PolicyValidationException(UAFErrorCode.PROTOCOL_POLICY_VALIDATION_FAILED);
       }
   }
 
   private int getMaxMatchCriteriasPerPolicy() throws PolicyVerificationException
   {
     return this.maxNumberOfMatchCriteriasAllowed;
   }
 }
